package core;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import mutations.Mutation;
import neural.Individual;
import neural.Net;
import select.Selection;
import crossover.Crossover;
import cutCriteria.CutCriteria;

public class Problem {

	private Integer populationSize;
	private Integer parentSize;
	private Integer maxGenerations;
	private Double mutationProbability;
	private Mutation mutation;
	private Crossover crossover;
	private Selection selection;
	private Comparator<Individual> comparator;
	private Selection replacement;
	private Integer generations;
	private CutCriteria criteria;
	private List<Individual> individuals;
	private double generationalBrecha;
	public List<Statistics> statisticList;
	private Double backPropagationProbability;

	// public Problem() {
	// initialize(null, null, null, null, null, null, null);
	// }

	public Problem(Integer populationSize,
			Integer maxGenerations, Double mutationProbability,
			Mutation mutation, Selection selection,
			Selection replacement, CutCriteria cutCriteria,
			Double generationalBrecha, Double backPropagationProbability) {

		initialize(populationSize, maxGenerations,
				mutationProbability, mutation, selection, replacement,
				cutCriteria, generationalBrecha, backPropagationProbability);

	}

	private void initialize(Integer populationSize2,
			Integer maxGenerations2, Double mutationProbability2,
			Mutation mutation2, Selection selection2,
			Selection replacement, CutCriteria cutCriteria,
			Double generationalBrecha, Double backPropagationProbability2) {
		populationSize = populationSize2;
		maxGenerations = maxGenerations2;
		mutationProbability = mutationProbability2;
		mutation = mutation2;
		selection = selection2;
		this.replacement = replacement;
		this.criteria = cutCriteria;
		this.generationalBrecha = generationalBrecha;

		generations = 0;
		individuals = generateIndividuals(populationSize2);
		backPropagationProbability = backPropagationProbability2;

		// criteria = new ArrayList<CutCriteria>();
		// criteria.add(new GenerationsCutCriteria());
		// criteria.add(new StructureCutCriteria(60)); // el percentage se
		// podr�a
		// cambiar ac�
		// criteria.add(new ContentCutCriteria(0.001,fitness)); // el
		// delta se podr�a cambiar ac�
		// criteria.add(new OptimumEnvironmentCutCriteria(null, 0.001, 10)); //
		// habr�a
		// que definir cu�l es el �ptimo, no s� si se puede saber de
		// antemano en
		// este problema.

	}

	private List<Individual> generateIndividuals(Integer populationSize2) {
		List<Individual> ret = new ArrayList<Individual>();
		for (int i = 0; i < populationSize2; i++) {
			ret.add(new Individual());
		}

		return ret;
	}

	public void solve() {
		/*
		 * final Plotter demo = new Plotter("grafiquito"); demo.pack();
		 * RefineryUtilities.centerFrameOnScreen(demo); demo.setVisible(true);
		 */
		System.out.println("Error de un individuo inicial: " + Net.getInstance().error(individuals.get(0).getWeights()));
		//System.out.println("Initial population: " + individuals);
		statisticList = new ArrayList<Statistics>();
		Set<List<Double>> old = new HashSet<List<Double>>(individuals.size());
		for(int i=0; i<individuals.size(); i++)
			old.add(individuals.get(i).getWeights());
		while (!criteria.shouldFinish(this)) {
			// while (true) {

			// selection.select(individuals, parentSize, fitness, null);
			Statistics statistic = new Statistics(generations);
			old.clear();
			System.out.println( individuals.size()+ " ind size");
			for(int i=0; i<individuals.size(); i++)
				old.add(individuals.get(i).getWeights());
			List<Individual> parents = selection.select(individuals,
					(int) ((generationalBrecha) * populationSize), comparator);

			// paso los padres double a ints! y luego los hijos a
			// double!!!!!!!!!!
			List<Individual> childs = crossover.cross(parents);
			childs = mutation.mutate(childs, this);
			
			for(Individual child: childs){
				if(Math.random() < backPropagationProbability){
					child.setWeights(Net.getInstance().backPropagation(child.getWeights(), 100));
				}
			}
			

			int selectedParentsQty = (int) ((1 - generationalBrecha) * populationSize);
			int selectedChildsQty = populationSize - selectedParentsQty;
			if (selectedChildsQty > childs.size()) {
				selectedChildsQty = childs.size();
				selectedParentsQty = populationSize - childs.size();
			}
			individuals = replacement.select(individuals, selectedParentsQty, comparator);
			//List<Individual> selectedChilds = replacement.select(childs,
			//		selectedChildsQty, fitness, comparator);
			individuals.addAll(childs);
//			System.out.println(old.size() + " old size antes de removed");
			Set<List<Double>> newinds = new HashSet<List<Double>>(individuals.size());
			for(int i=0; i<individuals.size(); i++)
				newinds.add(individuals.get(i).getWeights());
			
			Iterator<List<Double>> it = old.iterator();
			while(it.hasNext()){
				newinds.remove(it.next());
			}

			for(Individual in: individuals){
				statistic.addError(in.getFitness());
				
			}
			
//			System.out.println(old.size() + " old size removed");

			
			statistic.addVariation(newinds.size());
			
//			System.out.println("Generation: " + generations + ". Replacement: " + individuals);
			System.out.println("Generation: " + generations);
			generations++;
			System.out.println("best error" + statistic.findBest());
			statisticList.add(statistic);
			// demo.update(individuals);
		}

		//System.out.println("Last population" + individuals);

		Individual best = null;
		for (Individual d : individuals) {
			if (best == null) {
				best = d;
			} else if (comparator.compare(d, best) < 0) {
				best = d;
			}
		}
		//System.out.println("El mejor: " + best);
		System.out.println("Error de un individuo cualquiera final: " + Net.getInstance().error(individuals.get(0).getWeights()));
		System.out.println("El error del mejor: " + Net.getInstance().error(best.getWeights()));
		System.out.println("tamanio del mejor: " + best.getWeights().size());
		System.out.println("El fitness del mejor: " + best.getFitness());
		return;

	}
	
	public List<Statistics> getStatisticList(){
		return statisticList;
	}
	/*private List<Individual> removeDuplicated(List<Individual> replacement, List<Individual> induviduals){
		HashSet<Individual> removed = new HashSet<Individual>();
		removed.addAll(replacement);
		int cantidadRemovidos = replacement.size() - removed.size();
		
		List<Individual> nuevosAElegir = induviduals.removeAll(replacement);
		
		return induviduals;
	}*/

	// private boolean shouldCut() {
	// for (CutCriteria c : criteria) {
	// if (c.shouldFinish(this)) {
	// return true;
	// }
	// }
	// return false;
	// }

	public Integer getPopulationSize() {
		return populationSize;
	}

	public Integer getParentSize() {
		return parentSize;
	}

	public Integer getGenerations() {
		return generations;
	}

	public Double getMutationProbability() {
		return mutationProbability;
	}

	public Mutation getMutation() {
		return mutation;
	}

	public Selection getSelection() {
		return selection;
	}

	public Integer getMaxGenerations() {
		return maxGenerations;
	}

	public void setPopulationSize(Integer populationSize) {
		this.populationSize = populationSize;
	}

	public void setParentSize(Integer parentSize) {
		this.parentSize = parentSize;
	}

	public void setMaxGenerations(Integer generations) {
		this.maxGenerations = generations;
	}

	public void setMutationProbability(Double mutationProbability) {
		this.mutationProbability = mutationProbability;
	}

	public CutCriteria getCriteria() {
		return criteria;
	}

	public List<Individual> getIndividuals() {
		return individuals;
	}

	public void setMutation(Mutation mutation) {
		this.mutation = mutation;
	}

	public void setSelection(Selection selection) {
		this.selection = selection;
	}

	public Crossover getCrossover() {
		return crossover;
	}

	public void setCrossover(Crossover crossover) {
		this.crossover = crossover;
	}

	public Comparator<Individual> getComparator() {
		return comparator;
	}

	public void setComparator(Comparator<Individual> comparator) {
		this.comparator = comparator;
	}

}
